#ifndef MUSIC_H
#define MUSIC_H

#include "common_includes.h"

template<typename S>
class Music{
private:
	Music();
public:
	unsigned frame_size;
	vector<State> ground_truth;
	vector<array<S, NUM_STATES>> observation;
	string file_name;

	Music(const string& file_name){
		read_file(file_name);
		this->file_name = file_name;
	}

	void read_file(const string& file_name){
		ifstream file(file_name.c_str(), ios::in | ios::binary | ios::ate);
		if (file.is_open()){
			frame_size = (int) file.tellg() / (NUM_STATES + 1) / sizeof(float);
			file.seekg(0, ios::beg); // rewind to position 0

			observation.resize(frame_size);
			ground_truth.resize(frame_size);
			for (unsigned t = 0; t < frame_size; ++t){
				long double normalization = 0;
				for (unsigned i = 0; i < NUM_STATES; ++i){
					float temp;
					file.read((char*) &temp, sizeof(float));
					observation[t][i] = temp;
					normalization += observation[t][i];
				}
				normalization = 1 / normalization;
				for (unsigned i = 0; i < NUM_STATES; ++i){
					observation[t][i] *= normalization * 1000;
				}
				float memblock;
				file.read((char*) &memblock, sizeof(float));
				ground_truth[t] = int(memblock + 0.5);
				//originally, NC, Maj, Min,
				//but I'm using Maj, Min. NC
				if (ground_truth[t] == 0){
					ground_truth[t] = 24;
				}else{
					ground_truth[t] -= 1;
				}
				float tmp = observation[t][0];
				for (unsigned i = 0; i < NUM_STATES - 1; ++i){
					observation[t][i] = observation[t][i + 1];
				}
				observation[t][24] = tmp;
			}
			file.close();
		}else{
			cout << "Unable to open file" << endl;
		}
	}

	template<typename T>
	static vector<State> best_states(
			const vector<array<T, NUM_STATES>>& forward_backward
			, unsigned frame_size){
		vector<State> result(frame_size);
		for (unsigned t = 0; t < frame_size; ++t){
			State best_state = -1;
			T best_forward_backward = -numeric_limits<T>::infinity();
			for (unsigned i = 0; i < NUM_STATES; ++i){
				if (forward_backward[t][i] > best_forward_backward){
					best_state = i;
					best_forward_backward = forward_backward[t][i];
				}
			}
			result[t] = best_state;
			assert(0 <=best_state && best_state < NUM_STATES);
		}
		return result;
	}

	template<typename T>
	vector<State> best_path(const Transitions<T>& transitions) const{
		vector<array<State, NUM_STATES>> saved(frame_size);
		array<T, NUM_STATES> probability_temp1;
		array<T, NUM_STATES> probability_temp2;

		for (unsigned i = 0; i < NUM_STATES; ++i){
			probability_temp1[i] = log(observation[0][i]);
			saved[0][i] = -1;
		} // initial state

		for (unsigned t = 1; t < frame_size; ++t){
			for (unsigned i = 0; i < NUM_STATES; ++i){
				State max_state = -1;
				T max_probability = -numeric_limits<T>::infinity();
				for (unsigned j = 0; j < NUM_STATES; ++j){
					if (probability_temp1[j] + log(transitions.full[j][i])
							> max_probability){ //Todo what if >= ?
						max_probability = probability_temp1[j]
								+ log(transitions.full[j][i]);
						max_state = j;
					}
				}
				probability_temp2[i] = max_probability + log(observation[t][i]);
				saved[t][i] = max_state;
				assert(0 <=max_state && max_state < NUM_STATES);
			}
			probability_temp1 = probability_temp2;
		}

		//trace back
		vector<State> result(frame_size);
		State max_state = 0;
		T max_probability = 0;

		for (unsigned i = 0; i < NUM_STATES; ++i){
			if (probability_temp2[i] > max_probability){
				max_probability = probability_temp2[i];
				max_state = saved[frame_size - 1][i];
			}
		}
		result[frame_size - 1] = max_state;

		for (int t = frame_size - 2; t >= 0; --t){
			result[t] = saved[t + 1][result[t + 1]];
		}
		return result;
	}

	static unsigned path_similarity(const vector<State>& path1,
			const vector<State>& path2){
		assert(path1.size()==path2.size());
		unsigned score = 0;
		for (unsigned i = 0; i < path1.size(); ++i){
			score += (path1[i] == path2[i]);
		}
		return score;
	}

	template<typename T>
	static vector<T> score_framewise(
			const vector<array<T, NUM_STATES>>& forward_backward
			, unsigned frame_size
			, const vector<reference_wrapper<vector<State>>>& paths){ //Todo, better to get iterators instead of vectors and const?
				vector<T> result(paths.size(), 0);
				for (unsigned t = 0; t < frame_size; ++t){ // Todo frame_size-1??
					for (unsigned i=0; i<paths.size(); ++i){
						result[i] += forward_backward[t][paths[i].get()[t]];
					}
				}
				return result;
			}

			template<typename T>
			inline void forward_backward(const Transitions<T> & transitions,
					vector<array<T, NUM_STATES> > & result
					, vector<array<T, NUM_STATES> > & backward_temp, S best_state_bias) const{ //Todo state_bias type
				//Forward
				if (result.size() < frame_size){
					result.resize(frame_size);
				}
				//initialization
				{
					T normalization = 0; // scaling factors
					for (unsigned i = 0; i < NUM_STATES; ++i){
						normalization += observation[0][i];
					}
					normalization = 1 / normalization;
					for (unsigned i = 0; i < NUM_STATES; ++i){
						result[0][i] = observation[0][i] * normalization;
					}
				}
				//main loop
				for (unsigned t = 1; t < frame_size; ++t){
					T normalization = 0; // scaling factors
					for (unsigned i = 0; i < NUM_STATES; ++i){
						result[t][i] = 0;
						for (unsigned j = 0; j < NUM_STATES; ++j){
							result[t][i] += result[t - 1][j] * transitions.full[j][i];
						}
						result[t][i] *= observation[t][i];
						normalization += result[t][i];
					}

					normalization = 1 / normalization;
					for (unsigned i = 0; i < NUM_STATES; ++i){
						result[t][i] *= normalization;
					}
				}

				//Backward
				if (backward_temp.size() < frame_size){
					backward_temp.resize(frame_size);
				}
				//initialization
				for (unsigned i = 0; i < NUM_STATES; ++i){
					backward_temp[frame_size - 1][i] = 1.0 / NUM_STATES;
				}
				//main loop
				for (signed t = frame_size - 2; 0 <= t; --t){
					array<T, NUM_STATES> temp;
					for (unsigned j = 0; j < NUM_STATES; ++j){
						temp[j] = backward_temp[t + 1][j] * observation[t + 1][j];
					}
					T normalization = 0; // scaling factors
					for (unsigned i = 0; i < NUM_STATES; ++i){
						backward_temp[t][i] = 0;
						for (unsigned j = 0; j < NUM_STATES; ++j){
							backward_temp[t][i] += temp[j] * transitions.full[i][j];
						}
						normalization += backward_temp[t][i];
					}
					normalization = 1 / normalization;
					for (unsigned i = 0; i < NUM_STATES; ++i){
						backward_temp[t][i] *= normalization;
					}
				}

				//fb_combined
				for (unsigned t = 0; t < frame_size; ++t){
					T normalization = 0;
					for (unsigned i = 0; i < NUM_STATES; ++i){
						result[t][i] *= backward_temp[t][i];
						result[t][i] = pow(result[t][i], best_state_bias);
						normalization += result[t][i];
					}
					normalization = 1 / normalization;
					for (unsigned i = 0; i < NUM_STATES; ++i){
						result[t][i] *= normalization;
					}
				}
			}
		};
#endif
